
/* Targa reader/writer, revision 6
 * (c) 2001-2002, Emil Mikulic.
 *
 * This code is covered by the GNU GPL.
 */

#include "targa.h"
#include <stdlib.h>
#include <math.h>
#include <string.h>

char *tga_error; /* for passing errors */
//static char tga_id[] = "TRUEVISION X-FILE\x2E";

typedef struct {
	byte id_length;
	byte color_map_type;
	byte data_type_code;
	word color_map_origin;
	word color_map_length;
	byte color_map_depth;
	word origin_x;
	word origin_y;
	word width;
	word height;
	byte bits_per_pixel;
	byte image_descriptor;
} tga_header;



/* convert tga_image structure to a different format
 * returns NULL on failure
 * don't forget to deallocate [output]->data and [output] when done
 */
tga_image *tga_convert(	const tga_image *img,
			const byte bits_per_pixel,
			const tga_line_order line_order,
			const tga_color_order color_order)
{
	tga_image *out;
	int x,y;

	if (!img)
	{
		tga_error = "I was passed a NULL";
		return NULL;
	}

	if (	bits_per_pixel != 16 &&
		bits_per_pixel != 24 &&
		bits_per_pixel != 32)
	{
		tga_error = "I can only handle 16, 24 and 32-bit images";
		return NULL;
	}

	out = (tga_image *)malloc(sizeof(tga_image));
	if (!out)
	{
		tga_error = "Ran out of memory trying to allocate tga_image";
		return NULL;
	}

	out->width = img->width;
	out->height = img->height;
	out->bits_per_pixel = bits_per_pixel;
	out->color_order = color_order;
	out->line_order = line_order;

	out->data = (byte*)malloc(img->width * img->height *
		bits_per_pixel / 8);
	if (!(out->data))
	{
		tga_error = "Ran out of memory trying to allocate image data";
		return NULL;
	}

	for (y=0; y<img->height; y++)
	{
		int orig_y;

		if (img->line_order == line_order)
			orig_y = y;
		else
			orig_y = img->height - 1 - y;

		for (x=0; x<img->width; x++)
		{
			byte r=0,g=0,b=0,a=0;
			byte *src = img->data + (img->bits_per_pixel / 8) *
				(orig_y * img->width + x);
			byte *dest = out->data + (out->bits_per_pixel / 8) *
				(y * out->width + x);

			/* get RGBA values */
			if (img->bits_per_pixel == 16)
			{
				word z = *((word*)src);

				b =  z & 31;
				g = (z >> 5) & 31;
				r = (z >> 10) & 31;

				if (z && 32768)
					a = 255;
				else
					a = 0;

				r = r << 3;
				g = g << 3;
				b = b << 3;
			}
			else if ((img->bits_per_pixel == 24) ||
				 (img->bits_per_pixel == 32))
			{
				b = src[0];
				g = src[1];
				r = src[2];
				if (img->bits_per_pixel == 32)
					a = src[3];
				else
					a = 0;
			}

			/* swap color order if necessary */
			if (img->color_order != color_order)
			{
				byte t = r;
				r = b;
				b = t;
			}

			/* store RGBA */
			if (bits_per_pixel == 16)
			{
				word z;

				r = r >> 3;
				g = g >> 3;
				b = b >> 3;

				z =	 ((word)(b) & 31) |
					(((word)(g) & 31) << 5) |
					(((word)(r) & 31) << 10);

				*((word*)dest) = z;
			}
			else if ((bits_per_pixel == 24) ||
				 (bits_per_pixel == 32))
			{
				dest[0] = b;
				dest[1] = g;
				dest[2] = r;
				if (bits_per_pixel == 32)
					dest[3] = a;
			}
		}
	}

	return out;
}



/* just a helper function */
static int tga_header_write(FILE *fp, const tga_header *hdr)
{
	fwrite(&(hdr->id_length), 1, 1, fp);
	fwrite(&(hdr->color_map_type), 1, 1, fp);
	fwrite(&(hdr->data_type_code), 1, 1, fp);
	fwrite(&(hdr->color_map_origin), 2, 1, fp);
	fwrite(&(hdr->color_map_length), 2, 1, fp);
	fwrite(&(hdr->color_map_depth), 1, 1, fp);
	fwrite(&(hdr->origin_x), 2, 1, fp);
	fwrite(&(hdr->origin_y), 2, 1, fp);
	fwrite(&(hdr->width), 2, 1, fp);
	fwrite(&(hdr->height), 2, 1, fp);
	fwrite(&(hdr->bits_per_pixel), 1, 1, fp);
	if (fwrite(&(hdr->image_descriptor), 1, 1, fp) != 1)
	{
		tga_error = "Could not write to file";
		return 0;
	}
	else
		return 1;
}



/* write a tga_image structure into a Targa file
 * img : fully initialised tga_image structure
 *
 * returns zero or one on failure or success, respectively
 */
int tga_write(const char *filename,
		      const tga_image *img)
{
	tga_header hdr;
	tga_image *out;
	FILE *fp;

	if (img->bits_per_pixel != 16 &&
		img->bits_per_pixel != 24 &&
		img->bits_per_pixel != 32)
	{
		tga_error = "I can only handle 16, 24 and 32-bit images";
		return 0;
	}

	fp = fopen(filename, "wb");
	if (!fp)
	{
		tga_error = "Could not open file for writing";
		return 0;
	}

	hdr.id_length = 0;
	hdr.color_map_type = 0;
	hdr.data_type_code = 2;
	hdr.color_map_origin = 0;
	hdr.color_map_length = 0;
	hdr.color_map_depth = img->bits_per_pixel;
	hdr.origin_x = 0;
	hdr.origin_y = 0;
	hdr.width = img->width;
	hdr.height = img->height;
	hdr.bits_per_pixel = img->bits_per_pixel;
	hdr.image_descriptor = 0; /* 32 = top_to_bottom */

	if (img->color_order != BGR || img->line_order != BOTTOM_TO_TOP)
	{
		out = tga_convert(img, img->bits_per_pixel,
			BOTTOM_TO_TOP, BGR);
		if (!out) return 0;
	}
	else
		out = NULL;

	if (!tga_header_write(fp, &hdr))
	{
		fclose(fp);
		return 0;
	}

	if (out)
	{
		if (fwrite(out->data, out->width * out->height *
			out->bits_per_pixel/8, 1, fp) != 1)
		{
			tga_error = "Error writing image data to file";
			tga_free_img(&out);
			fclose(fp);
			return 0;
		}
		tga_free_img(&out);
	}
	else
	{
		if (fwrite(img->data, img->width * img->height *
			img->bits_per_pixel/8, 1, fp) != 1)
		{
			tga_error = "Error writing image data to file";
			fclose(fp);
			return 0;
		}
	}

	fclose(fp);
	return 1;
}



/* just a helper function */
static int tga_header_read(FILE *fp, tga_header *hdr)
{
	fread(&(hdr->id_length), 1, 1, fp);
	fread(&(hdr->color_map_type), 1, 1, fp);
	fread(&(hdr->data_type_code), 1, 1, fp);
	fread(&(hdr->color_map_origin), 2, 1, fp);
	fread(&(hdr->color_map_length), 2, 1, fp);
	fread(&(hdr->color_map_depth), 1, 1, fp);
	fread(&(hdr->origin_x), 2, 1, fp);
	fread(&(hdr->origin_y), 2, 1, fp);
	fread(&(hdr->width), 2, 1, fp);
	fread(&(hdr->height), 2, 1, fp);
	fread(&(hdr->bits_per_pixel), 1, 1, fp);
	if (fread(&(hdr->image_descriptor), 1, 1, fp) != 1)
	{
		tga_error = "Could not read from file";
		return 0;
	}
	else
		return 1;
}



/* free a tga_image structure and its image data
 * remember to pass the tga_image as a pointer to a pointer to the struct
 */
void tga_free_img(tga_image **img)
{
	if (*img != NULL) {
		if ((*img)->data != NULL) {
			free((*img)->data);
		}
		free(*img);
		*img = NULL;
	}
}



/* just a helper */
static int tga_read_rle_rgb(tga_image *img, FILE *fp)
{
	byte b, *pos;
	int p_loaded = 0, p_expected = img->width * img->height,
		bpp = img->bits_per_pixel/8;

	pos = img->data;

	while ((p_loaded < p_expected) && !feof(fp))
	{
		if (fread(&b, 1, 1, fp) != 1) return 0;
		if (b & 128) /* RLE packet */
		{
			byte count, tmp[4], i;

			count = (b & 127) + 1;
			if (fread(tmp, bpp, 1, fp) != 1) return 0;

			for (i=0; i<count; i++)
			{
				p_loaded++;
				if (p_loaded > p_expected) return 0;
				memcpy(pos, tmp, bpp);
				pos += bpp;
			}
		}
		else /* RAW packet */
		{
			byte count;

			count = (b & 127) + 1;
			if (p_loaded + count > p_expected) return 0;

			p_loaded += count;
			if (fread(pos, bpp, count, fp) != count) return 0;
			pos += count * bpp;
		}
	}

	return 1;
}



/* create a tga_image structure from reading a Targa file
 * returns NULL on failure
 * don't forget to deallocate [output]->data and [output] when done
 */
extern FILE *OpenBinaryFile(const char *filename);

tga_image *tga_read(const char *filename)
{
	FILE *fp;
	tga_header hdr;
	tga_image *img;

    fp = OpenBinaryFile(filename);
	if (!fp)
	{
		tga_error = "Can't open file for reading";
		return NULL;
	}

	if (!tga_header_read(fp, &hdr))
	{
		fclose(fp);
		return NULL;
	}

	/* nit-pick about the header */
	if (hdr.id_length != 0)
	{
		tga_error = "Can't handle an image ID";
		return NULL;
	}

	if (hdr.color_map_type != 0)
	{
		tga_error = "Can't handle colormapped images";
		return NULL;
	}

	if (	hdr.data_type_code != 2 &&
		hdr.data_type_code != 10)
	{
		switch (hdr.data_type_code)
		{
		case 0:
			tga_error = "No image data in file";
			break;
		case 1:
		case 9:
		case 32:
		case 33:
			tga_error = "Can't handle color-mapped images";
			break;
		case 3:
		case 11:
			tga_error = "Can't handle black-and-white images";
			break;
		default:
			tga_error = "Can't recognise data_type_code";
		}
		return NULL;
	}

	if (	(hdr.color_map_origin != 0) ||
		(hdr.color_map_length != 0) )
	{
		tga_error = "Can't handle colormap data";
		return NULL;
	}

	if ((hdr.origin_x != 0) || (hdr.origin_y != 0))
	{
		tga_error = "Can't handle non-zero origin";
		return NULL;
	}

	if ((hdr.width == 0) || (hdr.height == 0))
	{
		tga_error = "Botched image dimensions";
		return NULL;
	}

	if ((hdr.bits_per_pixel == 8) || (
			(hdr.bits_per_pixel != 16) &&
			(hdr.bits_per_pixel != 24) &&
			(hdr.bits_per_pixel != 32)) )
	{
		tga_error = "Can only handle 16, 24 and 32-bit images";
		return NULL;
	}

	/* done nit-picking, construct a new tga_image */
	img = (tga_image *)malloc(sizeof(tga_image));
	if (!img)
	{
		tga_error = "Ran out of memory trying to allocate tga_image";
		return NULL;
	}

	img->bits_per_pixel = hdr.bits_per_pixel;
	img->width = hdr.width;
	img->height = hdr.height;

	if (hdr.image_descriptor & 32)
		img->line_order = TOP_TO_BOTTOM;
	else
		img->line_order = BOTTOM_TO_TOP;
	img->color_order = BGR;
	img->data = (byte *)malloc(img->width * img->height *
		img->bits_per_pixel / 8);
	if (!(img->data))
	{
		tga_error = "Ran out of memory trying to allocate image data";
		return NULL;
	}

	/* load image */
	if (hdr.data_type_code == 2) /* unmapped RGB - easy peasy */
	{
		if (fread(img->data, img->width * img->height *
			img->bits_per_pixel/8, 1, fp) != 1)
		{
			tga_error = "Error reading image data from file";
			tga_free_img(&img);
			fclose(fp);
			return NULL;
		}
	}
	else
	if (hdr.data_type_code == 10) /* RLE RGB */
	{
		if (!tga_read_rle_rgb(img, fp))
		{
			tga_error = "Corrupt RLE data";
			tga_free_img(&img);
			fclose(fp);
			return NULL;
		}
	}
	else
	{
		tga_error = "Weird data_type_code perpetuated to loader";
		tga_free_img(&img);
		fclose(fp);
		return NULL;
	}

	fclose(fp);
	return img;
}

